/* 
 * File:   ParticleSystem.cpp
 * Author: RedEyedKiller
 * 
 * Created on 14 Ιούλιος 2010, 1:21 πμ
 */

#include <list>
#include "ParticleSystem.h"
#include "../Rect.h"
#include "../Camera.h"
#include "ParticleEmiter.h"
#include "../Utilities.h"
#include "../Logger.h"


namespace physicsSystem
{
namespace particleSystem
{

ParticleSystem::ParticleSystem()
{
    m_visible = true;
    lastInUseId = -1;
    firstRemovedId = -1;
    maxCachedEmiters = 20;
    SetAlwaysVisible( true );
}

void ParticleSystem::SetName(std::string& name)
{
    this->name = name;
}

std::string ParticleSystem::GetName() const
{
    return name;
}

void ParticleSystem::SetFirstRemovedId(int firstRemovedId)
{
    this->firstRemovedId = firstRemovedId;
}

int ParticleSystem::GetFirstRemovedId() const
{
    return firstRemovedId;
}

void ParticleSystem::SetLastInUseId(int lastInUseId)
{
    this->lastInUseId = lastInUseId;
}

int ParticleSystem::GetLastInUseId() const
{
    return lastInUseId;
}

void ParticleSystem::SetCreationThreashold(int creationThreashold)
{
    this->creationThreashold = creationThreashold;
}

int ParticleSystem::GetCreationThreashold() const
{
    return creationThreashold;
}

void ParticleSystem::SetPartsPerEmiter(unsigned long int partsPerEmiter)
{
    this->partsPerEmiter = partsPerEmiter;
}

unsigned long int ParticleSystem::GetPartsPerEmiter() const
{
    return partsPerEmiter;
}

void ParticleSystem::SetPrototype(ParticleEmiterPrototype prototype)
{
    this->prototype = prototype;
}

ParticleEmiterPrototype ParticleSystem::GetPrototype() const
{
    return prototype;
}

void ParticleSystem::SetActive(bool active)
{
    this->active = active;
}

bool ParticleSystem::IsActive() const
{
    return active;
}

void ParticleSystem::SetResource(std::string& resource)
{
    this->resource = resource;
}

const char* ParticleSystem::GetResource() const
{
    return resource.c_str( );
}

int ParticleSystem::AddPrototypedEmiterAt(const Math::Vector2F& pos, const Math::Vector2F& dir, bool oneTime /*=false*/)
{
    int id;
    //RemoveLastEmiters();
    //check if we need allocation or we can replace a dead emiter
    if( GetEmiterId( &id ) )
    {
        AddNewEmiter( new ParticleEmiter( &prototype, pos, dir, id ) );
    }
    else
    {
        ReplaceEmiter( pos, dir, id, false );
    }

    if( oneTime ) KillEmiter( id );
    return id;
}

bool ParticleSystem::GetEmiterId(int* id)
{
    if( firstRemovedId == -1 )
    {
        *id = ++lastInUseId;
        return true;
    }
    else
    {
        *id = firstRemovedId;
        firstRemovedId = FindNextRemoved( firstRemovedId );
        return false;
    }
}

void ParticleSystem::SetMaxCachedEmiters(int maxCachedEmiters)
{
    this->maxCachedEmiters = maxCachedEmiters;
}

int ParticleSystem::GetMaxCachedEmiters() const
{
    return maxCachedEmiters;
}

void ParticleSystem::SetMapCollisions(bool mapCollisions)
{
    this->mapCollisions = mapCollisions;
}

bool ParticleSystem::IsMapCollisions() const
{
    return mapCollisions;
}

void ParticleSystem::SetCameraBeh(CameraBehaviour cameraBeh)
{
    this->cameraBeh = cameraBeh;
}

ParticleSystem::CameraBehaviour ParticleSystem::GetCameraBeh() const
{
    return cameraBeh;
}

int ParticleSystem::GetEmiterId()
{
    return ++lastInUseId;
}

int ParticleSystem::FindNextRemoved(int from)
{
    int result = -1;
    int end;
    unsigned uMaxCachedEmiters = static_cast < unsigned > ( maxCachedEmiters );

    if( emiters.size( ) > uMaxCachedEmiters )
    {
        end = maxCachedEmiters;
    }
    else
    {
        end = emiters.size( );
    }

    for( int i = from + 1; i < end; ++i )
    {
        if( !emiters[i]->IsAlive( ) )
        {
            result = i;
            break;
        }
    }
    return result;
}

bool ParticleSystem::KillEmiter(int id)
{
    unsigned uID = static_cast < unsigned > ( id );

    if( uID >= emiters.size( ) )
    {
        //g++ is complaining about this, thus I disable it for the moment
        //LOGERROR << "Emiters vector out of bounds " << uID << " " << emiters.size() << " " << SOURCE;
        return false;
    }
    emiters[id]->Kill( );

    return true;
}

void ParticleSystem::Draw(sdl::GraphicsCore *gCore, Camera *camera)
{
    if( m_display != 0 && m_visible )
    {
        const gl::Texture& rsc = *m_display->GetRawData( );

        if( cameraBeh == CB_FOLLOW )
        {
            const Rect* rect = camera->GetRect( );
            {
                for( unsigned i = 0; i < emiters.size( ); i++ )
                {
                    if( !emiters[i]->IsAlive( ) ) continue;
                    emiters[i]->RenderWrapped( gCore, rsc, m_clip, *rect, m_blending );
                }
            }
        }
        else
        {
            const Rect* rect = camera->GetRect( );
            {
                for( unsigned i = 0; i < emiters.size( ); i++ )
                {
                    if( !emiters[i]->IsAlive( ) ) continue;
                    emiters[i]->Render( gCore, rsc, m_clip, *rect, m_blending );
                }
            }
        }
    }
}

ParticleSystem::~ParticleSystem()
{
    SafeRelease<ParticleEmiter*>( emiters );
}

void ParticleSystem::ApplyForcesToParticles(forces::Force* force)
{
    for( unsigned i = 0; i < emiters.size( ); ++i )
    {
        //if emiter is dead skip it.
        //Note that deactivated emiters still need update.
        if( !emiters[i]->IsAlive( ) ) continue;

        emiters[i]->ApplyForce( force );
    }
}

void ParticleSystem::UpdateParticles(unsigned long deltaTime)
{
    float seconds = deltaTime / 1000.0f;
    for( unsigned i = 0; i < emiters.size( ); ++i )
    {
        //if emiter is dead skip it.Note that deactivated emiters
        //still need update.
        if( !emiters[i]->IsAlive( ) ) continue;

        {//update particle emiters

            if( !emiters[i]->Update( seconds ) )
            {
                SubmitDeath( i );
            }
        }
    }
}

void ParticleSystem::RemoveLastEmiters()
{
    unsigned int size = emiters.size( ) - 1;
    if( size < maxCachedEmiters )
        return;
    for( unsigned i = size; i >= maxCachedEmiters; i-- )
    {
        //if emiter is alive stop the iteration
        if( emiters[i]->IsAlive( ) )
            break;
        //reduce last in use id
        lastInUseId--;
        //delete emiter
        delete emiters[i];
        //and remove it from the vector
        emiters.pop_back( );
        size--;
    }
}

void ParticleSystem::MoveEmiters(float x, float y)
{
    std::vector<ParticleEmiter*>::iterator emiter;
    for( emiter = emiters.begin( ); emiter != emiters.end( ); ++emiter )
    {
        if( !( *emiter )->IsActive( ) ) continue;
        ( *emiter )->Move( x, y );
    }
}

void ParticleSystem::MoveManipulators(float x, float y)
{
    //TODO: add move method here if field manipulators added
}

void ParticleSystem::MoveSystem(float x, float y, bool moveParticles)
{
    //position.Move(x, y);
    MoveEmiters( x, y );
}

void ParticleSystem::AddParticles()
{
    std::vector<ParticleEmiter*>::iterator emiter;
    for( emiter = emiters.begin( ); emiter != emiters.end( ); ++emiter )
    {
        //emiter should be alive in order to add particles
        if( !( *emiter )->IsAlive( ) ) continue;
        if( ( *emiter )->IsRecycler( ) )
        {
            ( *emiter )->AddParticlesViaRecycle( creationThreashold );
        }
        else
        {
            ( *emiter )->AddParticles( creationThreashold );
        }
    }
}

void ParticleSystem::TileCollision(LevelMap* map)
{
    std::vector<ParticleEmiter*>::iterator emiter;
    for( emiter = emiters.begin( ); emiter != emiters.end( ); ++emiter )
    {
        if( !( *emiter )->IsAlive( ) ) continue;
        ( *emiter )->TileCollisionCheck( map );
    }
}

}
}
